package main

import (
	"errors"
	"github.com/kataras/iris/v12"
	"github.com/kataras/iris/v12/mvc"
	"io/ioutil"
	"os"
	"sort"
	"strconv"
	"strings"
)

type Controller struct {
	Ctx iris.Context
}

func before(ctx iris.Context) {
	path := ctx.Request().RequestURI
	if strings.HasSuffix(path, "/") || ctx.GetHeader("X-Requested-With") == "XMLHttpRequest" {
		ctx.Next()
		return
	}
	ctx.Redirect(path + "/")
}

func (c *Controller) BeforeActivation(b mvc.BeforeActivation) {
	b.Handle("POST", "/{app:string}/{platform:string}/{eabi:string}/{version:string}", "PostApp")
	b.Handle("GET", "/", "GetApps")
	b.Handle("GET", "/{app:string}", "GetPlatforms", before)
	b.Handle("GET", "/{app:string}/{platform:string}", "GetEabis", before)
	b.Handle("GET", "/{app:string}/{platform:string}/{eabi:string}", "GetVersions", before)
	b.Handle("GET", "/{app:string}/{platform:string}/{eabi:string}/{version:string}", "GetApp")
	b.Handle("GET", "/{app:string}/{platform:string}/{eabi:string}/latest", "GetLatestApp")
}

func (c *Controller) PostApp(app string, platform string, eabi string, version string) {
	version, err := c.formatVersion(version)
	if err != nil {
		ErrorReply(&c.Ctx, 400, 1, err.Error())
		return
	}
	err = os.MkdirAll(storageDir+"/"+app+"/"+platform+"/"+eabi, os.ModePerm)
	if err != nil {
		ErrorReply(&c.Ctx, 500, 1, err.Error())
		return
	}
	body, err := c.Ctx.GetBody()
	if err != nil {
		ErrorReply(&c.Ctx, 500, 1, err.Error())
		return
	}
	file, err := os.Create(storageDir + "/" + app + "/" + platform + "/" + eabi + "/" + version)
	if err != nil {
		ErrorReply(&c.Ctx, 500, 1, err.Error())
		return
	}
	_, err = file.Write(body)
	if err != nil {
		ErrorReply(&c.Ctx, 500, 1, err.Error())
		return
	}
	err = file.Close()
	if err != nil {
		ErrorReply(&c.Ctx, 500, 1, err.Error())
		return
	}
	SuccessReply(&c.Ctx, "")
}

func (c *Controller) GetApps() {
	files, err := ioutil.ReadDir(storageDir)
	if err != nil {
		ErrorReply(&c.Ctx, 500, 1, err.Error())
		return
	}
	sort.Slice(files, func(i, j int) bool {
		return files[i].ModTime().Unix() > files[j].ModTime().Unix()
	})
	appItemReplies := make([]AppItemReply, len(files))
	for i := 0; i < len(appItemReplies); i++ {
		appItemReplies[i].Name = files[i].Name()
		appItemReplies[i].LastModified = files[i].ModTime()
		appItemReplies[i].Size = 0
		appItemReplies[i].Url = files[i].Name() + "/"
	}
	if xhrSign := c.Ctx.GetHeader("X-Requested-With"); xhrSign == "XMLHttpRequest" {
		SuccessReply(&c.Ctx, appItemReplies)
		return
	}
	appItemTemplateModels := make([]AppItemTemplateModel, len(appItemReplies))
	for i := 0; i < len(appItemTemplateModels); i++ {
		appItemTemplateModels[i].FromAppItemReply(&appItemReplies[i])
	}
	c.Ctx.ViewData("title", "Index of /")
	c.Ctx.ViewData("appItems", appItemTemplateModels)
	_ = c.Ctx.View("template.html")
}

func (c *Controller) GetPlatforms(app string) {
	files, err := ioutil.ReadDir(storageDir + "/" + app)
	if err != nil {
		ErrorReply(&c.Ctx, 500, 1, err.Error())
		return
	}
	sort.Slice(files, func(i, j int) bool {
		return files[i].ModTime().Unix() > files[j].ModTime().Unix()
	})
	appItemReplies := make([]AppItemReply, len(files))
	for i := 0; i < len(appItemReplies); i++ {
		appItemReplies[i].Name = files[i].Name()
		appItemReplies[i].LastModified = files[i].ModTime()
		appItemReplies[i].Size = 0
		appItemReplies[i].Url = files[i].Name() + "/"
	}
	if xhrSign := c.Ctx.GetHeader("X-Requested-With"); xhrSign == "XMLHttpRequest" {
		SuccessReply(&c.Ctx, appItemReplies)
		return
	}
	appItemTemplateModels := make([]AppItemTemplateModel, len(appItemReplies))
	for i := 0; i < len(appItemTemplateModels); i++ {
		appItemTemplateModels[i].FromAppItemReply(&appItemReplies[i])
	}
	c.Ctx.ViewData("title", "Index of /"+app+"/")
	c.Ctx.ViewData("appItems", appItemTemplateModels)
	_ = c.Ctx.View("template.html")
}

func (c *Controller) GetEabis(app string, platform string) {
	files, err := ioutil.ReadDir(storageDir + "/" + app + "/" + platform)
	if err != nil {
		ErrorReply(&c.Ctx, 500, 1, err.Error())
		return
	}
	sort.Slice(files, func(i, j int) bool {
		return files[i].ModTime().Unix() > files[j].ModTime().Unix()
	})
	appItemReplies := make([]AppItemReply, len(files))
	for i := 0; i < len(appItemReplies); i++ {
		appItemReplies[i].Name = files[i].Name()
		appItemReplies[i].LastModified = files[i].ModTime()
		appItemReplies[i].Size = 0
		appItemReplies[i].Url = files[i].Name() + "/"
	}
	if xhrSign := c.Ctx.GetHeader("X-Requested-With"); xhrSign == "XMLHttpRequest" {
		SuccessReply(&c.Ctx, appItemReplies)
		return
	}
	appItemTemplateModels := make([]AppItemTemplateModel, len(appItemReplies))
	for i := 0; i < len(appItemTemplateModels); i++ {
		appItemTemplateModels[i].FromAppItemReply(&appItemReplies[i])
	}
	c.Ctx.ViewData("title", "Index of /"+app+"/"+platform+"/")
	c.Ctx.ViewData("appItems", appItemTemplateModels)
	_ = c.Ctx.View("template.html")
}

func (c *Controller) GetVersions(app string, platform string, eabi string) {
	files, err := ioutil.ReadDir(storageDir + "/" + app + "/" + platform + "/" + eabi)
	if err != nil {
		ErrorReply(&c.Ctx, 500, 1, err.Error())
		return
	}
	sort.Slice(files, func(i, j int) bool {
		cmp := strings.Compare(files[i].Name(), files[j].Name())
		if cmp == 1 {
			return true
		}
		return false
	})
	appItemReplies := make([]AppItemReply, len(files))
	for i := 0; i < len(appItemReplies); i++ {
		version, err := c.unformatVersion(files[i].Name())
		if err != nil {
			ErrorReply(&c.Ctx, 500, 1, err.Error())
			return
		}
		appItemReplies[i].Name = version
		appItemReplies[i].LastModified = files[i].ModTime()
		appItemReplies[i].Size = uint64(files[i].Size())
		appItemReplies[i].Url = version
	}
	if xhrSign := c.Ctx.GetHeader("X-Requested-With"); xhrSign == "XMLHttpRequest" {
		SuccessReply(&c.Ctx, appItemReplies)
		return
	}
	appItemTemplateModels := make([]AppItemTemplateModel, len(appItemReplies))
	for i := 0; i < len(appItemTemplateModels); i++ {
		appItemTemplateModels[i].FromAppItemReply(&appItemReplies[i])
	}
	c.Ctx.ViewData("title", "Index of /"+app+"/"+platform+"/"+eabi+"/")
	c.Ctx.ViewData("appItems", appItemTemplateModels)
	_ = c.Ctx.View("template.html")
}

func (c *Controller) GetApp(app string, platform string, eabi string, version string) {
	formatVersion, err := c.formatVersion(version)
	if err != nil {
		ErrorReply(&c.Ctx, 500, 1, err.Error())
		return
	}
	filename := app + "_" + platform + "_" + eabi + "_" + version + "."
	switch strings.ToLower(platform) {
	case "ios":
		filename += "ipa"
		break
	case "android":
		filename += "apk"
		break
	default:
		filename += "exe"
		break
	}
	c.Ctx.Header("content-disposition", "attachment; filename=\""+filename+"\"")
	c.Ctx.ContentType("application/octet-stream")
	err = c.Ctx.ServeFile(storageDir + "/" + app + "/" + platform + "/" + eabi + "/" + formatVersion)
	if err != nil {
		ErrorReply(&c.Ctx, 500, 1, err.Error())
	}
}

func (c *Controller) GetLatestApp(app string, platform string, eabi string) {
	files, err := ioutil.ReadDir(storageDir + "/" + app + "/" + platform + "/" + eabi)
	if err != nil {
		ErrorReply(&c.Ctx, 500, 1, err.Error())
		return
	}
	sort.Slice(files, func(i, j int) bool {
		cmp := strings.Compare(files[i].Name(), files[j].Name())
		if cmp == 1 {
			return true
		}
		return false
	})
	version, err := c.unformatVersion(files[0].Name())
	if err != nil {
		ErrorReply(&c.Ctx, 500, 1, err.Error())
		return
	}
	filename := app + "_" + platform + "_" + eabi + "_" + version + "."
	switch strings.ToLower(platform) {
	case "ios":
		filename += "ipa"
		break
	case "android":
		filename += "apk"
		break
	default:
		filename += "exe"
		break
	}
	c.Ctx.Header("content-disposition", "attachment; filename=\""+filename+"\"")
	c.Ctx.ContentType("application/octet-stream")
	err = c.Ctx.ServeFile(storageDir + "/" + app + "/" + platform + "/" + eabi + "/" + files[0].Name())
	if err != nil {
		ErrorReply(&c.Ctx, 500, 1, err.Error())
	}
}

func (c *Controller) formatVersion(_version string) (version string, err error) {
	versionParts := strings.Split(_version, ".")
	for i := 0; i < len(versionParts); i++ {
		if len(versionParts) > 4 {
			err = errors.New("subversion too long, expect <= 4")
			return
		}
		for j := 0; j < len(versionParts[i]); j++ {
			if versionParts[i][j] < '0' || versionParts[i][j] > '9' {
				err = errors.New("subversion error, expect number")
				return
			}
		}
		switch len(versionParts[i]) {
		case 0:
			versionParts[i] = "0000"
			break
		case 1:
			versionParts[i] = "000" + versionParts[i]
			break
		case 2:
			versionParts[i] = "00" + versionParts[i]
			break
		case 3:
			versionParts[i] = "0" + versionParts[i]
			break
		default:
			break
		}
	}
	version = versionParts[0]
	for i := 1; i < len(versionParts); i++ {
		version += "." + versionParts[i]
	}
	return
}

func (c *Controller) unformatVersion(_version string) (version string, err error) {
	versionParts := strings.Split(_version, ".")
	for i := 0; i < len(versionParts); i++ {
		var num int
		num, err = strconv.Atoi(versionParts[i])
		if err != nil {
			return
		}
		versionParts[i] = strconv.Itoa(num)
	}
	version = versionParts[0]
	for i := 1; i < len(versionParts); i++ {
		version += "." + versionParts[i]
	}
	return
}
